package  
{
  import com.bit101.components.HUISlider;
  import com.bit101.components.List;
  import com.bit101.components.Panel;
  import com.bit101.components.PushButton;
  import com.bit101.components.RadioButton;
  import com.bit101.components.Window;
  import flash.display.Loader;
  import flash.display.NativeMenu;
  import flash.display.Sprite;
  import flash.events.ContextMenuEvent;
  import flash.events.Event;
  import flash.events.KeyboardEvent;
  import flash.events.MouseEvent;
  import flash.geom.Point;
  import flash.net.FileFilter;
  import flash.net.FileReference;
  import flash.ui.ContextMenu;
  import flash.ui.ContextMenuItem;
  import flash.ui.Keyboard;
  import flash.utils.ByteArray;
  import mge.graph.Edge;
  import mge.graph.Graph;
  import mge.graph.Node;
	/**
   * Displays a graph.
   * @author ivan
   */
  public class GraphStage extends Sprite
  {
    
    public function GraphStage() 
    {
      super();
      addEventListener(Event.ENTER_FRAME, draw);
      addEventListener(Event.ADDED_TO_STAGE, init);
      
      addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      
    }
    
    private function init(e:Event):void
    {
      removeEventListener(Event.ADDED_TO_STAGE, init);
      
      stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, true);
      
      new Scroller(this);
      
      var window:Window = new Window(stage, 0, 0, "Controls");
      
      radioButtonEdit = new RadioButton(window, 5, 5, "Edit", true, onEditMode);
      radioButtonNode = new RadioButton(window, 5, 20, "Node", false, onNodeMode);
      radioButtonEdge = new RadioButton(window, 5, 35, "Edge", false, onEdgeMode);
      
      var undoButton:PushButton = new PushButton(window, 0, 55, "undo", undo);
      var redoButton:PushButton = new PushButton(window, 30, 55, "redo", redo);
      undoButton.width = redoButton.width = 30;
      
      var exportButton:PushButton = new PushButton(window, 0, 85, "export", onExport);
      exportButton.width = 40;
      
      var importButton:PushButton = new PushButton(window, 40, 85, "import", onImport);
      importButton.width = 40;
      
      var nodeSize:HUISlider = new HUISlider(window, 0, 110, "Node Size", onNodeSizeChange);
      nodeSize.minimum = 1;
      nodeSize.maximum = 10;
      
      var editPanel:Panel = new Panel(window, 0, 130);
      editPanel.width = 60;
      editPanel.height = 30;
      var deleteButton:PushButton = new PushButton(editPanel, 5, 5, "delete", onDeleteSelection);
      deleteButton.width = 50;
      
      // Prepares context menu
      var cm:ContextMenu = new ContextMenu();
      cm.hideBuiltInItems();
      var loadBackgroundItem:ContextMenuItem = new ContextMenuItem("Load background");
      loadBackgroundItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onLoadBackground);
      cm.customItems.push(loadBackgroundItem);
      
      contextMenu = cm;
      
      /*
      console = new List(this);
      console.width = 200;
      console.height = 500;
      console.enabled = false;*/
      
      // stage.stageFocusRect = false;
      window.width = 150;
      window.height = 250;

      edgeTool = new EdgeTool(this);
      
      
      
      selection = new Selection(this);
      
      edgeTool.enabled = false;
      selection.enabled = true;
      
      addTools();
    }
    
    public function onLoadBackground(e:ContextMenuEvent):void
    {
      file = new FileReference();
      file.browse(new Array(new FileFilter("Image files", "*.jpg;*.gif;*.png"), new FileFilter("All (*.*)", "*.*")));
      file.addEventListener(Event.SELECT, _selectBackground);
    }
    
    private function _selectBackground(e:Event):void
    {
      file.addEventListener(Event.COMPLETE, _loadBackground);
      file.load();
    }
    
    private function _loadBackground(e:Event):void
    {
      var loader:Loader = new Loader();
      loader.loadBytes(file.data);
      
      while (Main.background.numChildren > 0)
        Main.background.removeChildAt(0);
        
      Main.background.addChild(loader);
      
      file = null;
    }
    
    
    private function onNodeSizeChange(e:Event):void
    {
      VisualNode.nodeSize = (e.target as HUISlider).value;
    }
    
    private function addTools():void
    {
      addChild(edgeTool);
      addChild(selection);
    }
    
    private function onExport(e:Event):void
    {      
      var t:String = "<graph>"
      for (var i:int = 0; i < numChildren; i++)
      {
        if (getChildAt(i) as VisualNode)
        {
          var vn:VisualNode = (getChildAt(i) as VisualNode);
          var node:Node = vn.node;
          t += '<node name="' + vn.node.name + '" x="' + node.x + '" y="' + node.y + '"></node>';
        }
        if (getChildAt(i) as VisualEdge)
        {
          var edge:Edge = (getChildAt(i) as VisualEdge).edge;
          t += '<edge source="' + edge.source.name + '" target="' + edge.target.name + '"></edge>';
        }
      }
      t += "</graph>";
      
      var xml:XML = new XML(t);
      
      var ba:ByteArray = new ByteArray();
      ba.writeUTFBytes(xml);
      var fr:FileReference = new FileReference();
      fr.save(ba, "graph.xml");
    }
    
    public function get graph():Graph
    {
      var graph:Graph = new Graph();
      for (var i:int = 0; i < numChildren; i++)
      {
        if (getChildAt(i) as VisualNode)
        {
          var vn:VisualNode = (getChildAt(i) as VisualNode);
          graph.nodes.push(vn.node);
        }
        if (getChildAt(i) as VisualEdge)
        {
          var edge:Edge = (getChildAt(i) as VisualEdge).edge;
          graph.edges.push(edge);
        }
      }
      return graph;
    }
    
    
    // TODO This could be done in a much nicer way if Serialization were able to work with Vector type.
    private var file:FileReference;
    private function onImport(e:Event):void
    {   
      file = new FileReference();
      file.browse(new Array(new FileFilter("XML files (*.xml)", "*.xml"), new FileFilter("All (*.*)", "*.*")));
      file.addEventListener(Event.SELECT, _selectFile);
    }    
    private function _selectFile(e:Event):void
    {
      file.addEventListener(Event.COMPLETE, _loadFile);
      file.load();
    }    
    private function _loadFile(e:Event):void
    {
      // Clear stage
      while (numChildren > 0)
        removeChildAt(0);
        
      addTools();
      
      // Load new file.
      var xml:XML = new XML(file.data);
      
      trace(xml);

      var nodes:Vector.<Node> = new Vector.<Node>();
      for each (var node:XML in xml..node)
      {
        var n:Node = new Node(node.@name, node.@x, node.@y);
        addNode(n);
        nodes.push(n);
      }
      
      for each (var edge:XML in xml..edge)
      {
        var source:Node;
        var target:Node;
        
        trace(edge.@source);
        trace(edge.@target);
        
        var sourceName:String = edge.@source;
        var targetName:String = edge.@target;
        
        for (var i:int = 0; i < nodes.length; i++) 
        {
          n = nodes[i];
          if (n.name == sourceName) source = n;
          if (n.name == targetName) target = n;
        }
        
        if (source && target)
          new AddEdgeCommand(this, new Edge(source, target));
      }
      
      file = null;
    }
    
    
    
    private function onDeleteSelection(e:Event = null):void
    {
      commands.push(new DeleteSelectionCommand(this));
      undoLevel = commands.length - 1;
    }
    
    private function onEditMode(e:Event = null):void
    {
      commands.push(new ModeCommand(this, MODE_EDIT));
      undoLevel = commands.length - 1;
    }
    
    private function onNodeMode(e:Event = null):void
    {
      commands.push(new ModeCommand(this, MODE_NODE));
      undoLevel = commands.length - 1;
    }
    
    private function onEdgeMode(e:Event = null):void
    {
      commands.push(new ModeCommand(this, MODE_EDGE));
      undoLevel = commands.length - 1;
    }
    
    private function draw(e:Event):void
    {
      // Just to have something to click on.
      graphics.clear();
      
      graphics.beginFill(0xBBBBBB, 0.5);
      graphics.drawRect(0, 0, 800, 600);
      graphics.endFill();
      
      if (console)
      {
        var array:Array = new Array;
        for (var i:int = 0; i < commands.length; i++)
          array.push(commands[i]);
        console.items = array;
        if (undoLevel > -1 && undoLevel < commands.length)
          console.selectedIndex = undoLevel;
      }
      
      // stage.focus = this;
      
    }
    
    private function undo(e:Event = null):void
    {
      if (undoLevel > -1)
          commands[undoLevel--].undo();
    }
    
    private function redo(e:Event = null):void
    {
      if (undoLevel < commands.length - 1)
          commands[++undoLevel].redo();
    }
    
    private function onKeyDown(e:KeyboardEvent):void
    {
      if (e.ctrlKey && e.keyCode == Keyboard.Z)
        undo();
      
      if (e.ctrlKey && e.keyCode == Keyboard.Y)
        redo();
      
      if (e.keyCode == Keyboard.NUMBER_1) onEditMode();
      if (e.keyCode == Keyboard.NUMBER_2) onNodeMode();
      if (e.keyCode == Keyboard.NUMBER_3) onEdgeMode();
    }
    
    private function onMouseDown(e:MouseEvent):void
    {
      if (e.target as VisualNode) trace("mouse down on " + (e.target as VisualNode).node.name);
      else trace("mouse down on " + e.target.name);
      if (e.target !== this)
      {
        // Mouse down over GraphStage element.
        //commands.push(new SelectCommand(this, e.target as Sprite));
        //undoLevel = commands.length - 1;
        
        return;
      }
      trace("GraphStage handling event...");
      
      switch(mode)
      {
        case MODE_EDIT:
        {
        }; break;
        
        case MODE_NODE:
        {
          nodeCount++;
          var node:Node = new Node(nodeCount.toString(), e.localX, e.localY);
          
          addNode(node);
        }; break;
      }
      
      
      var diff:int = commands.length - 1 - undoLevel;
      if (diff > 0)
        commands.splice(undoLevel + 1, diff);      
      
    }
    
    public function addNode(node:Node):void
    {
      commands.push(new AddNodeCommand(this, node));
      undoLevel = commands.length - 1;
    }
    
    private var nodeCount:int;
    
    public function dragSelection():void
    {
      commands.push( new DragSelectionCommand(this, new Point(selection.x, selection.y)) );
      undoLevel = commands.length - 1;
    }
    
    public function addEdge(edge:Edge):void
    {
      commands.push(new AddEdgeCommand(this, edge));
      undoLevel = commands.length - 1;
    }
    
    // tools
    public var selection:Selection;
    public var edgeTool:EdgeTool;
    
    private var commands:Vector.<Command> = new Vector.<Command>();
    private var undoLevel:int = -1;
    
    
    private var console:List;
    
    /*
     * Move and delete existing elements.
     * */
    public static const MODE_EDIT:String = "MODE_EDIT";
    
    /**
     * Add nodes.
     */
    public static const MODE_NODE:String = "MODE_NODE";
    
    /**
     * Add edges.
     */
    public static const MODE_EDGE:String = "MODE_EDGE";
    
    public var mode:String = MODE_EDIT;
    
    public var radioButtonEdit:RadioButton;
    public var radioButtonNode:RadioButton;
    public var radioButtonEdge:RadioButton;
    
  } 

}